#define _CRT_SECURE_NO_WARNINGS 1

// 自定义类型：结构体

// 匿名结构体类型
// 只能使用一次
//struct
//{
//	int a;
//	char b;
//	float c;
//} x;
//
//int main()
//{
//	struct s;
//
//	return 0;
//}

// 结构的自引用
//typedef struct 
//{
//	int data; // 存放数据
//	struct Node* next;
//}Node;
//

// 宏
// offserof -计算结构体成员相较于起始位置的偏移量
// 结构体的内存对齐
//
//#include <stdio.h>
//#include <stddef.h>
//struct S1
//{
//	char c1;
//	char c2;
//	int i;
//};
//
//struct S2
//{
//	char c1;
//	int i;
//	char c2;
//};
//
//struct S3
//{
//	double d;
//	char c;
//	int i;
//};
//
//struct S4
//{
//	char c1;
//	struct S3 s3;
//	double d;
//};
//
//// 为什么大小是有差异的呢?
//// 是怎么计算的呢？
//int main()
//{
//	printf("%zd\n", sizeof(struct S1)); // 8
//	printf("%zd\n", sizeof(struct S2)); // 12
//	printf("%zd\n", sizeof(struct S3)); // 16
//	printf("%zd\n", sizeof(struct S4)); // 32
//
//	printf("%zd\n", offsetof(struct S1, c1));
//	printf("%zd\n", offsetof(struct S1, c2));
//	printf("%zd\n", offsetof(struct S1, i));
//
//	return 0;
//}

// 1. 第一个成员在与结构体变量偏移量为0的地址处。
// 2. 其他成员变量要对齐到某个数字（对齐数）的整数倍的地址处。
//    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
//    VS中默认的值为8
//    Linux中gcc没有默认对齐数，对齐数就是成员自身的大小
// 3. 结构体总大小为最大对齐数（每个成员变量都有一个对齐数）的整数倍。
// 4. 如果嵌套了结构体的情况，嵌套的结构体对齐到自己成员中最大对齐数的整数倍处，结构体的整
//    体大小就是所有最大对齐数（含嵌套结构体的对齐数）的整数倍。

// 为什么存在内存对齐？
// 1.平台原因（移植原因）：
//   不是所有的硬件平台都能访问任意地址上的任意数据的；某些硬件平台只能在某些地址处
//   去某些特定类型的数据，否则抛出硬件异常
// 2.性能原因：
//   数据结构（尤其是栈）应该尽可能地在自然边界上对齐，原因在于，为了访问未对齐的内存
//   处理器需要作两次内存访问，而对齐的内存访问仅需要一次访问
//   假如一个处理器总是从内存中取8个字节，则地址必须是8的倍数
//   如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数，那么就可以用一个内存操作来读或者写值了
//   否则，我们可能需要执行两次内存访问，因为对象可能被分放在两个8字节内存块中

//   总的来说，结构体的内存对齐就是用空间换时间的做法
//   在设计结构体的时候，既要满足内存对齐，又要节省空间
//   因此就要让占用空间小的成员尽量集中在一起

// 修改默认对齐数
// #pragma pack(1) // 设置默认对齐数为1

//#include <stdio.h>
//#pragma pack(1) // 设置默认对齐数为1
//struct S1
//{
//	char c1;
//	char c2;
//	int i;
//};
//#pragma pack() // 取消设置的对齐数，还原为默认
//
//int main()
//{
//	printf("%zd\n", sizeof(struct S1));
//
//	return 0;
//}

// 结构体传参
//struct S
//{
//	int data[1000];
//	int num;
//};

//// 尽量传指针，函数传参的时候，参数是需要压栈的，会有时间和空间上的系统开销
//// 如果传递一个结构体对象的时候，结构体过大，参数压栈的系统开销比较大，会导致性能的下降

//void Print1(struct S t)
//{
//	printf("%d %d\n", t.data[0], t.num);
//}
//
//void Print2(const struct S* ps)
//{
//	printf("%d %d\n", ps->data[0], ps->num);
//}
//
//int main()
//{
//	struct S s = { {1,2,3,4,5},100 };
//
//	Print1(s);
//	Print2(&s);
//
//	return 0;
//}

// 结构体实现位段
// 位段的声明和结构是类似的
// 只是，位段的成员必须是int unsigned int,在C99中位段类型也可选择其他类型
// 位段的成员名后面有一个冒号和一个数字

// 位段的出现就是为了节省空间
// 位段的空间上是按照4个字节来开辟的
// 位段有很多不确定因素，位段是不跨平台的，注重可移植的程序应该避免使用位段
//#include <stdio.h>
//
//struct A
//{
//	// 47个bit位
//	int _a : 2; // bit位
//	int _b : 5; // bit位
//	int _c : 10; // bit位
//	int _d : 30; // bit位
//};
//
//int main()
//{
//	printf("%zd\n", sizeof(struct A));
//
//	return 0;
//}

// 从右向左使用，如果剩余的空间不够下一个成员使用，直接浪费
// 62 03 04
//#include <stdio.h>
//
//struct S
//{
//	char a : 3;
//	char b : 4;
//	char c : 5;
//	char d : 6;
//};
//
//int main()
//{
//	struct S s = { 0 };
//	s.a = 10;
//	s.b = 12;
//	s.c = 3;
//	s.d = 4;
//
//	printf("%zd\n", sizeof(s)); // 3
//	return 0;
//}

// 使用位段可能会遇到的跨平台问题
// int位段被当成有符号数还是无符号数是不确定的
// 位段中最大位的数目不能确定，（16位机器最大16，32位机器最大32，写成27，在16位机器就会出问题）
// 位段中的成员在内存中从右向左还是从左向右分配标准尚且未定义
// 当一个结构包含两个位段，第二个位段成员比较大，无法容纳第一个位段成员时候，是舍弃剩余比特位还是加以利用，不确定

// 位段的应用
// 有时候，比如IP数据报，很多属性仅需要几个bit位就能描述

// 位段的使用注意事项
// 不能对位段成员内部的成员使用取址符，因为一个字节内部的bit位是没有地址的